package joindemo.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.toolkit.Db;
// 【修改 1】引入 RedisCacheEnum
import joindemo.common.enums.RedisCacheEnum;
import joindemo.common.enums.ArticleVisibilityEnum;
import joindemo.common.enums.HttpStatusEnum;
import joindemo.converter.ArticleConverter;
import joindemo.domain.dto.ArticleDto;
import joindemo.domain.po.ArticlePo;
import joindemo.domain.po.UserPo;
import joindemo.domain.query.ArticlePageQuery;
import joindemo.domain.query.ArticleSearchQuery;
import joindemo.domain.vo.ArticleVo;
import joindemo.domain.vo.PageVo;
import joindemo.exception.ServiceException;
import joindemo.mapper.ArticleMapper;
import joindemo.service.IArticleService;
import joindemo.utils.RedisKeyUtil;
import joindemo.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Slf4j
@Service
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, ArticlePo> implements IArticleService {

    private final ArticleConverter articleConverter;
    private final RedisTemplate<String, Object> redisTemplate;

    public ArticleServiceImpl(ArticleConverter articleConverter,
                              RedisTemplate<String, Object> redisTemplate) {
        this.articleConverter = articleConverter;
        this.redisTemplate = redisTemplate;
    }

    // ==================== 文章详情 ====================

    @Override
    @Cacheable(value = RedisCacheEnum.CacheNames.ARTICLE, key = "#id")
    public ArticleVo getArticleById(Integer id) {
        if (id == null) {
            throw new ServiceException(HttpStatusEnum.BAD_REQUEST.getCode(), "文章ID不能为空");
        }
        log.debug("文章缓存未命中，查询数据库: id={}", id);
        ArticlePo articlePo = getById(id);
        if (articlePo == null) {
            log.warn("查询文章不存在: id={}", id);
            throw new ServiceException(HttpStatusEnum.NOT_FOUND);
        }

        String authorName = resolveAuthorName(articlePo.getUserId());
        return articleConverter.articlePoToArticleVo(articlePo, authorName);
    }

    // ==================== 增删改查  ====================

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CachePut(value = RedisCacheEnum.CacheNames.ARTICLE, key = "#result.id")
    public ArticleVo addArticle(ArticleDto articleDto, Long userId, String authorName) {
        if (userId == null) {
            throw new ServiceException(HttpStatusEnum.UNAUTHORIZED);
        }
        log.info("开始创建文章: user={}, title={}", authorName, articleDto.getTitle());

        ArticlePo articlePo = articleConverter.articleDtoToArticlePo(articleDto, userId);
        if (articlePo.getVisibility() == null) {
            articlePo.setVisibility(ArticleVisibilityEnum.PUBLIC);
        }
        save(articlePo);
        log.info("文章保存成功: id={}", articlePo.getId());

        return articleConverter.articlePoToArticleVo(articlePo, authorName);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CachePut(value = RedisCacheEnum.CacheNames.ARTICLE, key = "#id")
    public ArticleVo updateArticle(Integer id, ArticleDto articleDto, Long userId) {
        if (userId == null) {
            throw new ServiceException(HttpStatusEnum.UNAUTHORIZED);
        }
        ArticlePo exists = getById(id);
        if (exists == null) {
            throw new ServiceException(HttpStatusEnum.NOT_FOUND);
        }

        // 权限校验：仅作者本人可修改
        if (!exists.getUserId().equals(userId)) {
            log.warn("越权修改警告: user={} 尝试修改 article={} (owner={})", userId, id, exists.getUserId());
            throw new ServiceException(HttpStatusEnum.FORBIDDEN.getCode(), "无权修改此文章");
        }

        log.info("更新文章: id={}, newTitle={}", id, articleDto.getTitle());

        LambdaUpdateWrapper<ArticlePo> updateWrapper = new LambdaUpdateWrapper<ArticlePo>()
                .eq(ArticlePo::getId, id)
                .set(StringUtils.isNotBlank(articleDto.getTitle()), ArticlePo::getTitle, articleDto.getTitle())
                .set(StringUtils.isNotBlank(articleDto.getContent()), ArticlePo::getContent, articleDto.getContent())
                .set(StringUtils.isNotBlank(articleDto.getSummary()), ArticlePo::getSummary, articleDto.getSummary())
                .set(StringUtils.isNotBlank(articleDto.getCoverImage()), ArticlePo::getCoverImage, articleDto.getCoverImage())
                .set(articleDto.getVisibility() != null, ArticlePo::getVisibility, articleDto.getVisibility());
        update(null, updateWrapper);

        ArticlePo updated = getById(id);
        String authorName = resolveAuthorName(updated.getUserId());

        return articleConverter.articlePoToArticleVo(updated, authorName);
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = RedisCacheEnum.CacheNames.ARTICLE, key = "#id")
    public void deleteArticle(Integer id, Long userId) {
        if (userId == null) {
            throw new ServiceException(HttpStatusEnum.UNAUTHORIZED);
        }
        ArticlePo articlePo = getById(id);
        if (articlePo == null) {
            return;
        }
        if (!articlePo.getUserId().equals(userId)) {
            log.warn("越权删除警告: user={} 尝试删除 article={} (owner={})", userId, id, articlePo.getUserId());
            throw new ServiceException(HttpStatusEnum.FORBIDDEN.getCode(), "无权删除此文章");
        }
        removeById(id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = RedisCacheEnum.CacheNames.ARTICLE, key = "#id")
    public void deleteArticleByAdmin(Integer id) {
        ArticlePo articlePo = getById(id);
        if (articlePo == null) {
            return;
        }
        log.info("管理员执行强制删除: id={}, title={}", id, articlePo.getTitle());
        removeById(id);
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = RedisCacheEnum.CacheNames.ARTICLE, key = "#id")
    public void updateArticleVisibility(Integer id, ArticleVisibilityEnum visibility) {
        ArticlePo articlePo = getById(id);
        if (articlePo == null) {
            throw new ServiceException(HttpStatusEnum.NOT_FOUND);
        }
        log.info("修改文章可见性: id={}, visibility={}", id, visibility);
        articlePo.setVisibility(visibility);
        articlePo.setUpdatedAt(LocalDateTime.now());
        updateById(articlePo);
    }

    // ==================== 草稿箱 ====================

    @Override
    public String saveDraft(Long userId, String draftId, ArticleDto articleDto) {
        if (userId == null) {
            throw new ServiceException(HttpStatusEnum.UNAUTHORIZED);
        }
        if (articleDto == null) {
            throw new ServiceException(HttpStatusEnum.BAD_REQUEST.getCode(), "草稿内容不能为空");
        }
        String resolvedDraftId = StringUtils.isNotBlank(draftId) ? draftId : UUID.randomUUID().toString().replaceAll("-", "");
        log.info("保存草稿: user={}, draftId={}", userId, resolvedDraftId);

        String cacheKey = RedisKeyUtil.getArticleDraftKey(userId, resolvedDraftId);
        redisTemplate.opsForValue().set(cacheKey, articleDto, RedisCacheEnum.ARTICLE_DRAFT.getTtl());
        return resolvedDraftId;
    }

    @Override
    public ArticleDto getDraft(Long userId, String draftId) {
        if (userId == null) {
            throw new ServiceException(HttpStatusEnum.UNAUTHORIZED);
        }
        if (StringUtils.isBlank(draftId)) {
            throw new ServiceException(HttpStatusEnum.BAD_REQUEST.getCode(), "草稿ID不能为空");
        }
        String cacheKey = RedisKeyUtil.getArticleDraftKey(userId, draftId);
        Object cached = redisTemplate.opsForValue().get(cacheKey);
        if (cached instanceof ArticleDto) {
            return (ArticleDto) cached;
        }
        log.warn("草稿未找到: user={}, draftId={}", userId, draftId);
        throw new ServiceException(HttpStatusEnum.NOT_FOUND);
    }

    @Override
    public void deleteDraft(Long userId, String draftId) {
        if (userId == null) {
            throw new ServiceException(HttpStatusEnum.UNAUTHORIZED);
        }
        if (StringUtils.isBlank(draftId)) {
            throw new ServiceException(HttpStatusEnum.BAD_REQUEST.getCode(), "草稿ID不能为空");
        }
        log.info("删除草稿: user={}, draftId={}", userId, draftId);
        redisTemplate.delete(RedisKeyUtil.getArticleDraftKey(userId, draftId));
    }

    // ==================== 搜索与分页 ====================
    @Override
    public PageVo<ArticleVo> pageArticles(ArticlePageQuery query) {
        Page<ArticlePo> page = query.build();
        LambdaQueryWrapper<ArticlePo> wrapper = new LambdaQueryWrapper<ArticlePo>()
                .and(StringUtils.isNotBlank(query.getKeyword()), w -> w
                        .like(ArticlePo::getTitle, query.getKeyword())
                        .or()
                        .like(ArticlePo::getSummary, query.getKeyword())
                )
                .eq(query.getAuthorId() != null, ArticlePo::getUserId, query.getAuthorId())
                .eq(query.getVisibility() != null, ArticlePo::getVisibility, query.getVisibility())
                .eq(query.getVisibility() == null && Boolean.TRUE.equals(query.getOnlyPublic()),
                        ArticlePo::getVisibility, ArticleVisibilityEnum.PUBLIC)
                .ge(query.getStartTime() != null, ArticlePo::getCreatedAt, query.getStartTime())
                .le(query.getEndTime() != null, ArticlePo::getCreatedAt, query.getEndTime());
        Page<ArticlePo> result = this.page(page, wrapper);

        return buildPageVo(result);
    }

    @Override
    public PageVo<ArticleVo> searchArticles(ArticleSearchQuery query) {
        Page<ArticlePo> page = query.build();

        LambdaQueryWrapper<ArticlePo> wrapper = new LambdaQueryWrapper<ArticlePo>()
                .eq(ArticlePo::getVisibility, ArticleVisibilityEnum.PUBLIC)
                .and(StringUtils.isNotBlank(query.getKeyword()), w -> w
                        .like(ArticlePo::getTitle, query.getKeyword())
                        .or()
                        .like(ArticlePo::getSummary, query.getKeyword())
                        .or()
                        .like(ArticlePo::getContent, query.getKeyword())
                );

        Page<ArticlePo> result = this.page(page, wrapper);

        return buildPageVo(result);
    }

    private PageVo<ArticleVo> buildPageVo(Page<ArticlePo> result) {
        Map<Long, String> authorNames = loadAuthorNames(result.getRecords());
        List<ArticleVo> records = result.getRecords().stream()
                .map(item -> articleConverter.articlePoToArticleVo(item, authorNames.get(item.getUserId())))
                .collect(Collectors.toList());
        return PageVo.of(records, result.getTotal(), Math.toIntExact(result.getCurrent()), Math.toIntExact(result.getSize()));
    }

    private Map<Long, String> loadAuthorNames(List<ArticlePo> records) {
        if (CollectionUtils.isEmpty(records)) {
            return new ConcurrentHashMap<>();
        }
        Set<Long> userIds = records.stream()
                .map(ArticlePo::getUserId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        if (CollectionUtils.isEmpty(userIds)) {
            return new ConcurrentHashMap<>();
        }
        try {
            List<UserPo> users = Db.lambdaQuery(UserPo.class)
                    .in(UserPo::getId, userIds)
                    .list();
            return users.stream().collect(Collectors.toMap(UserPo::getId, UserPo::getUsername));
        } catch (Exception e) {
            log.error("加载作者信息失败，可能缺失User模块或UserPo", e);
            return new ConcurrentHashMap<>();
        }
    }

    private String resolveAuthorName(Long userId) {
        if (userId == null) {
            return null;
        }
        try {
            UserPo user = Db.lambdaQuery(UserPo.class)
                    .eq(UserPo::getId, userId)
                    .one();
            return user == null ? null : user.getUsername();
        } catch (Exception e) {
            log.error("解析作者信息失败: userId={}", userId, e);
            return "Unknown";
        }
    }
}